package dev.ideas.funkyj.support;

import dev.ideas.funkyj.function.F;
import dev.ideas.funkyj.function._Function;
import dev.ideas.funkyj.tuple.Pair;
import dev.ideas.funkyj.tuple.Trio;

import java.util.Iterator;

/**
 */
public class Iterators {
    //todo if only these could be composed (iterator with prefetch and filter) ...

    public static class PairIterator<T1, T2> implements Iterator<Pair<T1, T2>> {
        final Iterator<T1> i1;
        final Iterator<T2> i2;

        public PairIterator(final Iterator<T1> i1, final Iterator<T2> i2) { this.i1 = i1; this.i2 = i2; }

        public boolean hasNext() { return i1.hasNext() && i2.hasNext(); }
        public Pair<T1, T2> next() { return Pair.of(i1.next(), i2.next()); }
        public void remove() { i1.remove(); i2.remove(); }
    }

    public static class TrioIterator<T1, T2, T3> implements Iterator<Trio<T1, T2, T3>> {
        final Iterator<T1> i1;
        final Iterator<T2> i2;
        final Iterator<T3> i3;

        public TrioIterator(final Iterator<T1> i1, final Iterator<T2> i2, final Iterator<T3> i3) { this.i1 = i1; this.i2 = i2; this.i3 = i3; }

        public boolean hasNext() { return i1.hasNext() && i2.hasNext() && i3.hasNext(); }
        public Trio<T1, T2, T3> next() { return Trio.of(i1.next(), i2.next(), i3.next()); }
        public void remove() { i1.remove(); i2.remove(); i3.remove();}
    }

    public static class FilterIterator<X> implements Iterator<X> {
        private Iterator<?> from;
        private final F/*<?, Y>*/ f;
        private X next = null;

        enum State { hasNext, next, end }
        State state = State.hasNext;

        private final X eos = null;

        public FilterIterator(final Iterator<?> iterator, final F f) {
            from = iterator;
            this.f = f;
        }

        public boolean hasNext() {
            prepareNext();
            return state == State.next;
        }

        private void prepareNext() {
            if (state == State.hasNext) {
                while (from.hasNext()) {
                    //noinspection unchecked
                    next = (X) f.apply(from.next());
                    if (next != eos) { state = State.next; return; }
                }
                state = State.end;
            }
        }

        public X next() {
            state = State.hasNext;
            return next;
        }

        public void remove() {
            from.remove();
        }

        @Override public String toString() {
            return f.toString() + "(" + from + ") (" + next + ")";
        }
    }

    public static class ForkIterator<T1, X> implements Iterator<T1> {
        final Iterator<X> iterator;
        Iterator<T1> current;
        private final _Function<X, Iterator<T1>> f;

        public ForkIterator(final Iterator<X> iterator, _Function<X, Iterator<T1>> f) {
            this.f = f;
            this.iterator = iterator;
            current = proceed();
        }

        private Iterator<T1> proceed() {
            return iterator.hasNext() ? f.apply(iterator.next()) : null;
        }

        public boolean hasNext() {
            while (!current.hasNext())
                if ((current = proceed()) == null) return false;
            return true;
        }
        public T1 next() { return current.next(); }
        public void remove() { current.remove(); }
    }

    public static class PairToTrioIterator<T1, T2, T3> implements Iterator<Trio<T1, T2, T3>> {
        Iterator<Pair<T1, T2>> i12;
        Iterator<T3> i3;

        public PairToTrioIterator(final Iterable<Pair<T1, T2>> it12, Iterable<T3> it3) {
            i12 = it12.iterator();
            i3 = it3.iterator();
        }

        public boolean hasNext() { return i12.hasNext() && i3.hasNext(); }
        public Trio<T1, T2, T3> next() {
            Pair<T1, T2> pair = i12.next();
            return Trio.of(pair.first(), pair.second(), i3.next());
        }
        public void remove() { i12.remove(); i3.remove(); }
    }
}
